#pragma once

#include <algorithm>
#include <random>
#include <time.h>

/*
	Describes any MxN playing card deck where
	M is the number of "face values" and
	N is the number of "suits".
	It's assumed that each face value is distributed across all suits,
	even if some of those face values are considered suitless,
	such as the +4 cards in UNO.
	You must parameterize this class with enums that include
	FACE_VALUE_COUNT and SUIT_COUNT.
*/
template<typename ValueType, typename SuitType>
class PlayingCard
{
public:
					PlayingCard(
						ValueType	the,
						SuitType	of
					) :
						faceValue(the),
						suit(of)
					{}

	/*
		Gives the count of the cards in the deck under the assumptions:
			1) Every face value appears as many times as there are suits.
			2) By #1, all face values have equal distribution.
			3) There are only two dimensions, face values and suits, to this deck.
		It's also assumed that the enumerated types used to parameterize
		this class contain constants that count the number of face values
		and suits, and that those constants have specific names.
		@return quantity of face values * quantity of suits
	*/
	static
	unsigned
	int				count()
					{
						return FACE_VALUE_COUNT * SUIT_COUNT;
					} // end class method

	/*
		Produce an MxN amount of cards, using the assumptions given in count(),
		and return them in a new PlayingCard array.
		@param fMin		the lowest face value
		@param sMin		the lowest suit value
		@return			a one-dimensional array filled with two-dimensional cards
	*/
	static
	PlayingCard**	generateDeck(int fMin, int sMin)
					{
						int deckSize = count();
						PlayingCard** deck = new PlayingCard*[deckSize];
						PlayingCard** card = deck + deckSize;

						for (int s = sMin + SUIT_COUNT; s-- != sMin;)
							for (int f = fMin + FACE_VALUE_COUNT; f-- != fMin;)
								*(--card) = new PlayingCard(
									static_cast<ValueType>(f), static_cast<SuitType>(s));

						return deck;
					} // end class method

	// faceValue accessor
	ValueType		getFaceValue() { return faceValue; }
	
	// suit accessor
	SuitType		getSuit() { return suit; }

	/*
		Jumble the (unordered?) array of PlayingCards into a random order.
		The procedure I have decided to adopt starts a 'bookmark' at the 'end' of the deck,
		and -- as it inches forward -- it decides which card in the range
		'front' to 'bookmark' should replace the bookmark.
		This should theoretically give every card an equal chance to appear
		at any location, though I admit to being uncertain about it.
		One thing that might be a problem is that rand() only produces 2^16
		numbers, which is less than deckCount-factorial.
		This algorithm is in-place, so the parameter is essentially a return value.
		@param deck		array of PlayingCard pointers
	*/
	static
	void			shuffle(
						PlayingCard**	deck
					) {
						unsigned short cardCount = count();

						srand(static_cast<unsigned int>(time(0)));

						do
							std::swap(deck[rand() % cardCount], deck[--cardCount]);
						while (1 != cardCount);
					} // end class method

	/*
		Order the cards according to their face value,
		while simultaneously ordering the suits by their arbitrary cardinality.
		This algorithm is performed in-place.  Its input is overwritten by its output.
		@param deck		array of PlayingCard pointers to be sorted
	*/
	static
	void			sort(PlayingCard**	deck)
					{
						std::sort(deck, deck + count(), compare);
					} // end class method

protected:
	ValueType		faceValue;
	SuitType		suit;

	/*
		STL comparison for PlayingCards
		@param a	pointer to the "first" item
		@param b	pointer to the "second" item
		@return		true if first is evaluated to be less than second
					This evaluation is completely arbitrary,
					since cards don't have an order between suits.
					The enumerated types that are being supplied
					are providing the cardinality by which Value and Suit are judged.
	*/
	static
	bool			compare(
						PlayingCard* a,
						PlayingCard* b
					) {
						SuitType aSuit = a->getSuit();
						SuitType bSuit = b->getSuit();
						bool sameSuit = (aSuit == bSuit);
						return (sameSuit ? a->getFaceValue() < b->getFaceValue() : aSuit < bSuit);
					} // end class method
}; // end class